/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Implementation for class CMaCICtrlData
 * \author Antti Maula <antti.maula@tkk.fi>
 *
 * $Id: MaCICtrlData.cpp,v 1.15 2009-05-13 07:18:17 amaula Exp $
 *
 */
#include "MaCICtrlData.hpp"
#include "owndebug.h"

//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
using namespace MaCI::MaCICtrl;
//*****************************************************************************
OWNDEBUG_INITIALIZE_MODULENUMBER(); // Initialize module print capability
//*****************************************************************************

CMaCICtrlData::CMaCICtrlData()
  : iTimestampPtr(NULL),
    iCommandPtr(NULL),
    iGroupIDPtr(NULL),
    iAccessPtr(NULL),
    iAuthenticationPtr(NULL),
    iDeviceGroupPtr(NULL),
    iServicePtrArray(),
    iServiceIdentificationPtrMap()
{
}
//*****************************************************************************

CMaCICtrlData::~CMaCICtrlData()
{
}
//*****************************************************************************

bool CMaCICtrlData::DecodeFrom(gim::binbag::CBinBag *aBinBag)
{
  bool result = true;
  const gim::binbag::TBinBlob *b = NULL;
  int previous_type = -1;
  
  // Store pointer and test it for NullNess
  if (aBinBag != NULL)
    iBinBagContainer = aBinBag;

  // Parse
  if (iBinBagContainer && iBinBagContainer->Rewind()) {
    while( (b = iBinBagContainer->GetNextBinary()) && result ) {
      switch(b->type) {
      case KTypeTimestamp:
	if (iTimestampPtr == NULL) {
	  iTimestampPtr = reinterpret_cast<const Common::TTimestamp *>(b->data);
	} else result = false;
	break;

      case KTypeCommand: {
        if (iCommandPtr == NULL) {
          iCommandPtr = reinterpret_cast<const TCommand *>(b->data); 
        } else result = false;
        break;
      }
        
      case KTypeDeviceGroup: {
        // GroupID ptr must be NULL and DeviceArray size must be zero
        // OR previous type must be SubGroup. This requires that
        // DeviceSubgroups are added in sequence.
        if (iGroupIDPtr == NULL &&
            iDeviceGroupPtr == NULL) {
          iDeviceGroupPtr = reinterpret_cast<const TDeviceGroup *>(b->data); 
        } else result = false;
        break;
      }
        
      case KTypeGroupID: {
        // SubGroupArray must be empty AND GroupID must be NULL,
        if (iDeviceGroupPtr == NULL &&
            iGroupIDPtr == NULL) {
          iGroupIDPtr = reinterpret_cast<const TGroupID *>(b->data); 
        } else result = false;
        break;
      }

      case KTypeService: {
        // Require; array empty, or previous element Service or
        // ServiceIdentification.
        if (iServicePtrArray.size() == 0 || 
            previous_type == KTypeService ||
            previous_type == KTypeServiceIdentification) {
          const TService *sptr = reinterpret_cast<const TService *>(b->data); 
          iServicePtrArray.push_back(sptr);
         
        } else result = false;
        break;
      }

      case KTypeServiceIdentification: {
        // Require previous type always Service.
        if (previous_type == KTypeService) {
          assert(iServicePtrArray.size() > 0);
	 const TService *ss = iServicePtrArray.back();
	 TService sss;	 
#ifdef MACICTRLDATA_BROKEN_STRUCT_COPY
#warning "Using 'MACICTRLDATA_BROKEN_STRUCT_COPY' for copying table field-by-field"
	 sss.servicemajor=ss->servicemajor;
	 sss.serviceminor=ss->serviceminor;
	 sss.type=ss->type;
	 sss.flags=ss->flags;
#else
	 sss = *ss;
#endif
          const std::pair<TService, const TServiceIdentification *> 
            n(sss, 
              reinterpret_cast<const TServiceIdentification *>(b->data));
          iServiceIdentificationPtrMap.insert(n);

        } else result = false;
        break;
      }

      case KTypeAccess: {
        if (iAccessPtr == NULL) {
          iAccessPtr = reinterpret_cast<const TAccess *>(b->data); 
        }
        break;
      }

      case KTypeAuthentication: {
        if (iAuthenticationPtr == NULL) {
          iAuthenticationPtr = reinterpret_cast<const TAuthentication *>(b->data); 
        }
        break;
      }

      default:
	// Unknown Type in BinBag. Do nothing.
	dPrintM(10,"Unknown type %u, ignoring.", b->type);
        break;
      }
      // Store previous type
      previous_type = b->type;
    }
  } else result = false;
  // Store the result
  SetReadOnlyContainer(result);

  return result;
}
//*****************************************************************************

void CMaCICtrlData::Reset()
{
  // Clean out. (Same values as in default constructor)
  iTimestampPtr = NULL;
  iCommandPtr = NULL;
  iGroupIDPtr = NULL;
  iServicePtrArray.clear();
  iDeviceGroupPtr = NULL;
  iServiceIdentificationPtrMap.clear();
  SetLastElementType(KTypeUnknown);
}
//*****************************************************************************

bool CMaCICtrlData::SetTimestamp(const Common::TTimestamp &aStamp)
{
  bool result = false;
  if (!IsReadOnlyContainer() && iTimestampPtr == NULL) {
    ADDELEMENT(Common::TTimestamp, KTypeTimestamp, iTimestampPtr, aStamp);
    result = true;
  }
  return result;
}
//*****************************************************************************

bool CMaCICtrlData::SetCommand(const TCommand &aCmd)
{  
  bool result = false;
  if (!IsReadOnlyContainer() && iCommandPtr == NULL) {
    ADDELEMENT(TCommand, KTypeCommand, iCommandPtr, aCmd);
    result = true;
  } 
  return result;
}
//*****************************************************************************

bool CMaCICtrlData::SetGroupID(const TGroupID &aId)
{
  dPrintLCRed(ODERROR,"WARNING: Deprecated function '%s' called!", __FUNCTION__);
  bool result = false;
  if (!IsReadOnlyContainer() && 
      iGroupIDPtr == NULL &&
      iDeviceGroupPtr == NULL) {
    ADDELEMENT(TGroupID, KTypeGroupID, iGroupIDPtr, aId);
    result = true;
  }
  return result;
}
//*****************************************************************************

bool CMaCICtrlData::SetAccess(const TAccess &aAccess)
{ 
  bool result = false;
  if (!IsReadOnlyContainer() && iAccessPtr == NULL) {
    ADDELEMENT(TAccess, KTypeAccess, iAccessPtr, aAccess);
    result = true;
  }
  return result;
}
//*****************************************************************************

bool CMaCICtrlData::SetAuthentication(const TAuthentication &aAuth)
{ 
  bool result = false;
  if (!IsReadOnlyContainer() && iAuthenticationPtr == NULL) {
    ADDELEMENT(TAuthentication, KTypeAuthentication, iAuthenticationPtr, aAuth);
    result = true;
  }
  return result;
}
//*****************************************************************************

bool CMaCICtrlData::AddService(const TService &aService, 
                               const TServiceIdentification &aServiceIdentification)
{
  bool result = false;

  // Execute if; Not readonly and either ServicePtr is NULL or 
  // last element was of same type. (Ensure sequence)
  if (!IsReadOnlyContainer() && 
      ( iServicePtrArray.size() == 0 || GetLastElementType() == KTypeService )) {
    
    const gim::binbag::TBinBlob *b = 
      iBinBagContainer->AddBinary(KTypeService, (const char *)&aService, 
				  sizeof(TService));
    
    // IF the Identification is set, add it to container.
    if (aServiceIdentification.interfaceinstancename[0]) {
      iBinBagContainer->AddBinary(KTypeServiceIdentification, 
                                  (const char *)&aServiceIdentification, 
                                  sizeof(TServiceIdentification));
    }
    
    // Push in array.
    iServicePtrArray.push_back(reinterpret_cast<const TService *>(b->data));

    // Mark last element
    SetLastElementType(KTypeService);
    
    result = true;
  }

  return result;
}
//*****************************************************************************

bool CMaCICtrlData::SetDeviceGroup(const TDeviceGroup &aGroup)
{
  bool result = false;

  // There must be NO iGroupIDPtr set and the container must not be
  // Read-Only.
  if (!IsReadOnlyContainer() &&
      iGroupIDPtr == NULL &&
      iDeviceGroupPtr == NULL) {
    
    // Insertion.
    const gim::binbag::TBinBlob *b = 
      iBinBagContainer->AddBinary(KTypeDeviceGroup, 
                                  (const char *)&aGroup, 
                                  aGroup.GetContainerSize());
    iDeviceGroupPtr = reinterpret_cast<const TDeviceGroup *>(b->data);
    SetLastElementType(KTypeDeviceGroup);
    result = true;
  }
  return result;
}
//*****************************************************************************

bool CMaCICtrlData::SetDeviceGroup(const std::string &aGroup)
{  
  bool result = false;
  uint8_t *ptr = NULL;
  unsigned int bytes = 0;
  
  // Create dynamic container from xmlString
  TDeviceGroup *tdsg = TDeviceGroup::CreateTextElement(&ptr, bytes, aGroup);
  
  // Use the normal setter function. (use result)
  result = SetDeviceGroup(*tdsg);
  
  // Unconditionally destroy dynamic element.
  TDeviceGroup::DestroyTextElement(tdsg);
  
  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

const MaCI::Common::TTimestamp *CMaCICtrlData::GetTimestamp(void) const
{
  return iTimestampPtr;
}
//*****************************************************************************

const TCommand *CMaCICtrlData::GetCommand(void) const
{
  return iCommandPtr;
}
//*****************************************************************************

const TGroupID *CMaCICtrlData::GetGroupID(void) const
{
  return iGroupIDPtr;
}
//*****************************************************************************

const TAccess *CMaCICtrlData::GetAccess(void) const
{
  return iAccessPtr;
}
//*****************************************************************************

const TAuthentication *CMaCICtrlData::GetAuthentication(void) const
{
  return iAuthenticationPtr;
}
//*****************************************************************************

const TService *CMaCICtrlData::GetService(const unsigned int aIndex) const
{
  const TService *result = NULL;

  // Check service count.
  if (IsReadOnlyContainer() && 
      aIndex < iServicePtrArray.size() ) {
    // Now, Get result.
    result = iServicePtrArray[aIndex];
    
  } else {
    dPrintM(10, "Not ReadOnly or Index out-of-bounds!");

  }

  return result;
}
//*****************************************************************************

const TServiceIdentification *CMaCICtrlData::GetServiceIdentification(const TService &aService) const
{
  const TServiceIdentification *result = NULL;
  
  // Check service count.
  if (IsReadOnlyContainer() && iServicePtrArray.size() ) {
    // Now, Get result.
    const ITERATOR(iServiceIdentificationPtrMap) sipmi = iServiceIdentificationPtrMap.find(aService);
    if (sipmi != iServiceIdentificationPtrMap.end()) {
      result = sipmi->second;

    } else {
      dPrintM(8,"No matching ServiceIdentification found! (Size of map is %u elements)",iServiceIdentificationPtrMap.size());
      for(EACH_IN_i(iServiceIdentificationPtrMap)) {
        
      }
      
    }

  } else {
    dPrintM(10, "Not ReadOnly or iServicePtrArray empty!");

  }

  return result;
}

//*****************************************************************************

const TDeviceGroup *CMaCICtrlData::GetDeviceGroup() const
{
  return iDeviceGroupPtr;
}
//*****************************************************************************

std::string CMaCICtrlData::GetDeviceGroupString() const
{
  std::string result;
  const TDeviceGroup *dg = GetDeviceGroup();
  if (dg) {
    result = std::string(dg->text,
                         dg->length-1);
  }
  return result;
}
//*****************************************************************************

void CMaCICtrlData::Print(const int l) const
{ 
  const gim::binbag::TBinBlob *b = NULL;
  // Parse
  if (iBinBagContainer && iBinBagContainer->Rewind()) {
    while( (b = iBinBagContainer->GetNextBinary())) {
      switch(b->type) {

      case KTypeTimestamp: {
        const Common::TTimestamp *ts = 
          reinterpret_cast<const Common::TTimestamp *>(b->data);
	dPrint(l,"TTimestamp : %d, %d", 
	       ts->timestamp_s,
	       ts->timestamp_us);
        break;
      }
        
      case KTypeCommand: {
        const TCommand *e = 
          reinterpret_cast<const TCommand *>(b->data);
	dPrint(l,"TCommand : %d", 
	       e->cmd);
        break;
      }

      case KTypeDeviceGroup: {
        const TDeviceGroup *dsg = 
          reinterpret_cast<const TDeviceGroup *>(b->data);
        dPrint(l,"TDeviceGroup: '%s'", dsg->text);
        break;
      }
        
      case KTypeGroupID: {
        const TGroupID *e = 
          reinterpret_cast<const TGroupID *>(b->data);
	dPrint(l,"TGroupID : '%s', %u", 
	       e->name, e->id);
        break;
      }
        
      case KTypeService: {
        const TService *e = 
          reinterpret_cast<const TService *>(b->data);
	dPrint(l,"TService : %u, %u", 
	       e->servicemajor, e->serviceminor);
        break;
      }
        
      case KTypeAccess: {
        const TAccess *a = 
          reinterpret_cast<const TAccess *>(b->data);
	dPrint(l,"TAccess : %x", 
	       a->access);
        break;   
      }
 
      case KTypeAuthentication: {
        /*        const TAuthentication *a = 
                  reinterpret_cast<const TAuthentication *>(b->data);*/
	dPrint(l,"TAuthentication : Got It :)");
        break;   
      }
        
      default:
	// Unknown Type in BinBag. Do nothing.
        dPrintM(10,"Don't know what to do with typeID: %u", b->type);
	break;
      }
    }
  }
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

#ifdef COMPILE_MACICTRLDATA_STANDALONE_MAIN
#include <string>

int main(void)
{
  using namespace MaCI::MaCICtrl;
  gim::binbag::CBinBag bb;
  CMaCICtrlData A;
  CMaCICtrlData B;
  bool r;

  // Constants for add loops.
  const unsigned int servicecount = 10;

  debugInit();
  debugSetGlobalDebugLvl(2);

  printf("Carry out Encoding/Decoding test.\n");

  printf("Encoding...\n");
  // Do Encoding 
  A.EncodeTo(&bb);
  
  printf("Encode: Timestamp(1,2) ... ");
  r = A.SetTimestamp(MaCI::Common::TTimestamp(1, 2));
  assert(r == true);
  printf("OK.\n");

  printf("Encode: TCommand(KCommandGetInformation) ... ");
  r = A.SetCommand(TCommand(KCommandGetInformation));
  assert(r == true);
  printf("OK.\n");

  // If selected TEST_GROUPID, test it. otherwise test TDeviceSubgroup
#ifdef MACICTRLDATA_TEST_GROUPID
  printf("Encode: TGroupID(666, \"Foobar\" ... ");
  r = A.SetGroupID(TGroupID(666, "Foobar"));
  assert(r == true);
  printf("OK.\n");
#else
  // Print.
  printf("Encode: TDeviceGroup('MilkyWay.Earth.Finland.Otaniemi.TUAS.J2B2') ... ");
  // Add element & test.
  r = A.SetDeviceGroup("MilkyWay.Earth.Finland.Otaniemi.TUAS.J2B2");
  assert(r == true);
  printf("OK.\n");
#endif // MACICTRLDATA_TEST_GROUPID
  
  for(unsigned int i=0; i < servicecount; ++i) {
    printf("Encode: TService(%u, %u) ... ", i, 2*i);
    r = A.AddService(TService(i,2*i)); 
    assert(r == true);
    printf("OK.\n");
  }


  //////////////////////////////////////////////////////////
  printf("Decoding...\n");
  // Do Decoding
  B.DecodeFrom(&bb);
  printf("Decode: GetTimestamp() = (%d, %d)\n", 
	 B.GetTimestamp()->timestamp_s, 
	 B.GetTimestamp()->timestamp_us);
  assert(B.GetTimestamp()->timestamp_s == 1);
  assert(B.GetTimestamp()->timestamp_us == 2);
   

  printf("Decode: GetCommand() == (%d)\n", 
         B.GetCommand()->cmd);
  assert(B.GetCommand()->cmd == KCommandGetInformation);

  const TService *s;
  unsigned int si = 0;
  printf("Decode: Decoding Services, should have %u services available\n",
         B.GetServiceCount());
  while( ( s = B.GetService(si) ) != NULL ) {
    printf("Decode: GetService(%u) == (%u, %u)\n",
           si,
           s->servicemajor,
           s->serviceminor);
    assert(s->servicemajor == si);
    assert(s->serviceminor == 2*si);
    ++si;
  }

#ifdef MACICTRLDATA_TEST_GROUPID
  printf("Decode: GetGroupID() == (%d, '%s')\n",
         B.GetGroupID()->id,
         B.GetGroupID()->name);
  assert(B.GetGroupID()->id == 666);
  assert(std::string(B.GetGroupID()->name) == std::string("Foobar"));
#else
  printf("Decode: GetDeviceGroupString() == ('%s')\n",
         B.GetDeviceGroupString().c_str());
  assert(B.GetDeviceGroupString() == std::string("MilkyWay.Earth.Finland.Otaniemi.TUAS.J2B2"));
#endif // MACICTRLDATA_TEST_GROUPID

  printf("Test complete.\n");
  

}
#endif
//*****************************************************************************
//*****************************************************************************
